//
// File:   ParamsArray.h
// Author: scohen
//
// Created on August 27, 2007, 12:22 PM
//

#ifndef _PARAMSARRAY_H
#define	_PARAMSARRAY_H

#include <map>
#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <stdlib.h>
using namespace std;

class ParamsConstraint {
public:
    ParamsConstraint(string p, string d, bool o) {
        param = p;
        desc = d;
        optional = o;
    }
    
    string param;
    string desc;
    bool optional;
};


/**
 * ParamsArray is a class for handling command-line parameters.
 * The parameters in the command line are accepted in the form
 * of <code>name=value name=value name=value</code> ...
 * where name is the parameter's name and value is its value.
 * You can ommit the parameter's name (and the equal sign), in
 * which case the parameter's name will be <code>argX</code> where X is the index
 * of the parameter in the command-line, starting with 0.
 * A special parameter name is "loadparams" which its value must be
 * a filename. In that case, the file is loaded into the parameters
 * list.
 *
 * @author	Shay Cohen
 */

class ParamsArray {
private:
    map<string, string> paramsHash;
    vector<string> paramsList;
    vector<ParamsConstraint> paramsConstraints;
    string description;
    
    /**
     * A constructor for creating a parameters list.
     *
     * @param args	Array of parameters from the command-line.
     */
public:
    ParamsArray(int argc, char** argv) {
        for (int i=0; i<argc; i++) {
            addParam(argv[i]);
        }
    }
    
    /**
     * A constructor for creating an empty parameters list.
     */
    ParamsArray() {
    }

	vector<string> tokenize(const string& str,const string& delimiters) {
        vector<string> tokens;

        // skip delimiters at beginning.
        string::size_type lastPos = str.find_first_not_of(delimiters, 0);

        // find first "non-delimiter".
        string::size_type pos = str.find_first_of(delimiters, lastPos);
        
        while (string::npos != pos || string::npos != lastPos) {       
                // found a token, add it to the vector.
                tokens.push_back(str.substr(lastPos, pos - lastPos));
                
                // skip delimiters.  Note the "not_of"
                lastPos = str.find_first_not_of(delimiters, pos);
                
                // find next "non-delimiter"
                pos = str.find_first_of(delimiters, lastPos);
        }    
        return tokens;
	}
    
    /**
     * Adds a new parameter to the parameter list.
     *
     * @param param Paramater in the form of name=value
     */
    void addParam(string param) {
        string paramValue, paramName;
        
        vector<string> tokens = tokenize(param, "=");
        
        if (tokens.size() == 1) {
            paramValue = param;
            paramName = "arg"+paramsList.size();
        } else {
            if (tokens[0].length() == 0) {
                paramName = "arg"+paramsList.size();
            } else {
                paramName = tokens[0];
            }
            
            paramValue = param.substr(tokens[0].length()+1);
        }
        
        
        /*		if (paramName.equals("loadparams"))
         * {
         * ParamsArray* array = new ParamsArray();
         *
         * array.load(paramValue);
         * merge(array);
         *
         * delete array;
         * } else {*/
        paramsList.push_back(paramName);
        paramsHash[paramName] = paramValue;
        //}

	if (paramName == "randseed") {
		cerr<<"Setting seed to "<<asInt(paramName)<<endl;
		if (paramValue == "time") {
			srand((unsigned int)time(NULL));
		} else {
			srand(asInt(paramName));
		}
	}
    }
    
    /**
     * Returns a parameter as integer.
     *
     * @param paramName Name of parameter to return
     */
    int asInt(string paramName) {
        if (paramsHash.find(paramName) == paramsHash.end()) { return -1; }
        return atoi(paramsHash[paramName].c_str());
    }
    
    /**
     * Returns a parameter as integer with default value.
     *
     * @param paramName Name of parameter to return
     * @param d Default value to return
     */
    int asInt(string paramName, int d) {
        if (paramsHash.find(paramName) == paramsHash.end())
            return d;
        
        int i;
        i = atoi(paramsHash[paramName].c_str());
        
        return i;
    }
    
    /**
     * Returns a parameter as integer with default option.
     *
     * @param paramNum Index of parameter to return
     * @param d Default value to return
     */
    int asInt(unsigned int paramNum, int d) {
        if (paramNum >= paramsList.size()) return -1;
        return asInt(paramsList[paramNum], d);
    }
    
    /**
     * Returns a parameter as integer.
     *
     * @param paramNum Index of parameter to return
     */
    int asInt(unsigned int paramNum) {
        if (paramNum >= paramsList.size()) return -1;
        return asInt(paramsList[paramNum]);
    }
    
    /**
     * Returns a parameter as double.
     *
     * @param paramName Name of parameter to return
     */
    double asDouble(string paramName) {
        if (paramsHash.find(paramName) == paramsHash.end()) { return -1; }
        
        return atof(paramsHash[paramName].c_str());
    }
    
    /**
     * Returns a parameter as double with default value.
     *
     * @param paramName Name of parameter to return
     * @param d Default value to return
     */
    double asDouble(string paramName, double d) {
        if (paramsHash.find(paramName) == paramsHash.end())
            return d;
        return atof(paramsHash[paramName].c_str());
    }
    
    /**
     * Returns a parameter as double with default option.
     *
     * @param paramNum Index of parameter to return
     * @param d Default value to return
     */
    double asDouble(unsigned int paramNum, double d) {
        if (paramNum >= paramsList.size()) return 0;
        return asDouble(paramsList[paramNum], d);
    }
    
    /**
     * Returns a parameter as double.
     *
     * @param paramNum Index of parameter to return
     */
    double asDouble(unsigned int paramNum) {
        if (paramNum >= paramsList.size()) return 0;
        return asDouble(paramsList[paramNum]);
    }
    
    /**
     * Returns a parameter as String with default value.
     *
     * @param paramName Name of parameter to return
     * @param d Default value to return
     */
    string asString(string paramName, string d) {
        if (paramsHash.find(paramName) == paramsHash.end())
            return d;
        return paramsHash[paramName];
    }
    
    /**
     * Returns a parameter as string
     *
     * @param paramName Name of parameter to return
     */
    string asString(string paramName) {
        if (paramsHash.find(paramName) == paramsHash.end()) { return ""; }
        
        return paramsHash[paramName];
    }
    
    
    /**
     * Returns a parameter as string
     *
     * @param paramNum Number of parameter to return
     */
    string asString(unsigned int paramNum) {
        if (paramNum >= paramsList.size()) return "";
        return asString(paramsList[paramNum]);
    }
    
    /**
     * Returns the name of a parameter
     *
     * @param paramNum Number of parameter to return its name
     */
    string getParamName(unsigned int paramNum) {
        if (paramNum >= paramsList.size()) return "";
        return paramsList[paramNum];
    }
    
    /**
     * Returns the number of parameters in the list
     */
    int length() {
        return paramsList.size();
    }
    
    
    /**
     * Adds a list of parameters to the current list
     *
     * @param otherArray Another list of parameters to add
     */
    void merge(ParamsArray* otherArray) {
        for (int i=0; i<otherArray->length(); i++) {
            string paramName = otherArray->getParamName(i);
            if (paramName.substr(0, 3) == "arg") {
                paramName = "";
            }
            
            addParam(paramName+"="+otherArray->asString(i));
        }
    }
    
    /**
     * Saves the parameters list to a file.
     * File contains param=value separated by returns.
     *
     * @param filename File name of parameters
     */
    /*public void save(String filename) {
     * Writer output = null;
     * try {
     * output = new BufferedWriter( new FileWriter(new File(filename)) );
     * for (int i=0; i<length(); i++) {
     * output.write(getParamName(i)+"="+asString(i)+"\n");
     * }
     * }
     * catch (IOException e) {
     * System.err.println("Could not write params file "+filename);
     * }
     * finally {
     * try {
     * if (output!= null) {
     * output.close();
     * }
     * }
     * catch (IOException ex) {
     * System.err.println("Could not save "+filename);
     * }
     * }
     *
     * }*/
    
    /**
     * Loads the parameters list from a file.
     * File should contain param=value separated by returns.
     *
     * @param filename File name of parameters
     */
    /*public void load(String filename) {
     * BufferedReader input = null;
     *
     * try {
     * input = new BufferedReader( new FileReader(new File(filename)) );
     * String line = null;
     * while (( line = input.readLine()) != null){
     * addParam(line);
     * }
     * }
     * catch (IOException ex){
     * System.err.println("Could not load params file "+filename);
     * }
     *
     * finally {
     * try {
     * if (input!= null) {
     * input.close();
     * }
     * }
     * catch (IOException ex) {
     * System.err.println("Could not load "+filename);
     * }
     * }
     * }
     */
    
    /**
     * Converts the parameters list to a CR separated list
     * of the form name=value.
     */
    /*public String toString() {
     * String s = "";
     *
     * for (int i=0; i<paramsList.size(); i++) {
     * s = s + paramsList.get(i) + "=" + asString(paramsList.get(i))+"\n";
     * }
     *
     * return s;
     * }*/
    
    void addConstraint(string param, string desc, bool optional) {
        paramsConstraints.push_back(ParamsConstraint(param, desc, optional));
    }
    
    bool runConstraints(string programName = "", string revision = "") {
        bool paramsOk = true;
        
        if ((programName != "") || (revision != "")) {
            cerr<<programName<<" "<<revision<<endl;
        }
        
        for (unsigned int i=0; i<paramsConstraints.size(); i++) {
            ParamsConstraint c = (ParamsConstraint)paramsConstraints[i];
            if ((c.optional == false) && (asString(c.param)=="")) {
                if (paramsOk) {
                    cerr<<description<<endl;
                    cerr<<"Parameters:"<<endl;
                    for (unsigned int j=0; j<paramsConstraints.size(); j++) {
                        ParamsConstraint c2 = (ParamsConstraint)paramsConstraints[j];
                        cerr<<c2.param<<" - "<<c2.desc;
                        if (c2.optional) {
                            cerr<<" (optional)"<<endl;
                        } else {
                            cerr<<endl;
                        }
                    }
                    cerr<<"parameters are passed using param1=value1 param2=value2 ..."<<endl;
                }
                cerr<<c.param<<" not set"<<endl;
                paramsOk = false;
            }
        }
        
        if (paramsOk) {
            cerr<<"Parameters list:"<<endl;
            for (unsigned int i=0; i<paramsConstraints.size(); i++) {
                ParamsConstraint c = (ParamsConstraint)paramsConstraints[i];
                if (asString(c.param) == "") {
                    cerr<<c.param<<" - [default]"<<endl;
                } else {
                    cerr<<c.param<<" - "<<asString(c.param)<<endl;
                }
            }
            cerr<<endl;
        }
        
        return paramsOk;
    }
    
    void setDescription(string d) {
        description = d;
    }
};

#endif	/* _PARAMSARRAY_H */

